package com.yfp.client.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.NumberUtil;
import com.github.binarywang.wxpay.bean.notify.WxPayPartnerRefundNotifyV3Result;
import com.yfp.client.domain.*;
import com.yfp.client.domain.*;
import com.yfp.client.domain.dto.PayPasswordDTO;
import com.yfp.client.domain.dto.PayPasswordFreeDTO;
import com.yfp.client.domain.vo.ClientBalanceVO;
import com.yfp.client.enums.ClientIntegralStatusEnum;
import com.yfp.client.mapper.ClientMapper;
import com.yfp.client.service.ClientBillService;
import com.yfp.client.service.ClientHistoryInfoService;
import com.yfp.client.service.ClientIntegralService;
import com.yfp.client.service.ClientService;
import com.yfp.client.util.*;
import com.yfp.common.core.constant.CacheConstants;
import com.yfp.common.core.exception.ServiceException;
import com.yfp.common.core.utils.StringUtils;
import com.yfp.common.redis.service.RedisService;
import com.yfp.operation.api.RemoteOperationService;
import com.yfp.client.util.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.MessageFormat;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @Description: 客户Service
 *
 * @CreateDate: 2024/5/16 9:59
 * @UpdateUser:
 * @UpdateDate: 2024/5/16 9:59
 * @UpdateRemark: 暂无修改
 * @Version: 1.0
 */
@Service
@Slf4j
public class ClientServiceImpl implements ClientService {

    @Resource
    private AliyunSmsConf aliyunSmsConf;
    @Resource
    private ClientMapper clientMapper;
    @Resource
    private RedisService redisService;
    @Resource
    private ClientBillService clientBillService;
    @Resource
    private RemoteOperationService remoteOperationService;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ClientHistoryInfoService clientHistoryInfoService;
    @Resource
    private ClientIntegralService clientIntegralService;

    @Override
    public Client getById(String id) {
        Client client = clientMapper.getById(id);
        if (null != client && null == client.getIntegral()){
            client.setIntegral(0);
        }
        return client;
    }

    @Override
    public Client getByOpenId(String openId) {
        return clientMapper.getByOpenId(openId);
    }

    @Override
    public void add(Client client) {
        clientMapper.insert(client);
    }

    @Override
    public Client findByMobile(String mobile) {
        return clientMapper.findByMobile(mobile);
    }

    @Override
    @Transactional
    public void edit(Client client) {
        log.info("edit-client{}", client);
        // 检查数值是否发生变动，如果数值有变动做记录
        clientHistoryInfoService.saveClientHistoryInfoCompareOld(client);

        clientMapper.edit(client);
    }

    @Override
    public void clearOpenId(String id) {
        clientMapper.clearOpenId(id);
    }

    @Override
    public void clearMobile(String id) {
        clientMapper.clearMobile(id);
    }

    @Override
    public List<Client> clientList() {
        List<Client> list = clientMapper.clientList();
        return list;
    }

    @Override
    public List<Client> getByIds(List<String> clientIdList) {
        if (CollUtil.isEmpty(clientIdList)) {
            return CollUtil.newArrayList();
        }

        return clientMapper.getByIds(clientIdList);
    }

    @Override
    public ClientBalanceVO balance() {
        String clientId = SecurityUtils.getClientId();
        log.info("用户id:{}", clientId);
        Client client = clientMapper.getById(clientId);
        ClientBalanceVO clientBalance = BeanUtil.toBean(client, ClientBalanceVO.class);

        BigDecimal totalBalance = NumberUtil.add(clientBalance.getBalance(), clientBalance.getGiftGold());
        clientBalance.setTotalBalance(totalBalance);

        Boolean firstStored = clientBillService.isFirstStored();
        clientBalance.setFirstStored(BooleanUtil.toInteger(firstStored));
        return clientBalance;
    }

    @Override
    public Boolean hasPayPassword() {
        String clientId = SecurityUtils.getClientId();
        Client client = clientMapper.getById(clientId);
        return Objects.nonNull(client.getPayPassword());
    }

    @Override
    public void setPayPassword(PayPasswordDTO payPasswordDTO) {
        // 校验两次密码是否一致
        if (!StringUtils.equals(payPasswordDTO.getFirstPassword(), payPasswordDTO.getSecondPassword())) {
            throw new ServiceException("两次密码输入不一致，请重新设置");
        }
        String clientId = SecurityUtils.getClientId();
        String payPassword = SecurityUtils.encryptPassword(payPasswordDTO.getFirstPassword());
        clientMapper.updatePayPassword(clientId, payPassword);
    }

    @Override
    public Integer payPasswordFree() {
        String clientId = SecurityUtils.getClientId();
        Client client = clientMapper.getById(clientId);
        Integer passwordFree = client.getPasswordFree();
        if (Objects.isNull(passwordFree)) {
            return 0;
        }
        return passwordFree;
    }

    @Override
    public void payPasswordFree(PayPasswordFreeDTO payPasswordFreeDTO) {
        String clientId = SecurityUtils.getClientId();
        // 开启需要输入支付密码，输入正确后按钮开启
        if (payPasswordFreeDTO.getPayPasswordFree() == 1) {
            Client client = clientMapper.getById(clientId);
            if (!SecurityUtils.matchesPassword(payPasswordFreeDTO.getPassword(), client.getPayPassword())) {
                throw new ServiceException("支付密码错误，请重新输入");
            }
        }
        // 关闭点击按钮，直接关闭
        else if (payPasswordFreeDTO.getPayPasswordFree() == 0) {

        }

        // 修改免密支付按钮状态
        clientMapper.updatePasswordFree(clientId, payPasswordFreeDTO.getPayPasswordFree());
    }

    @Override
    public void resetPayPasswordGetCode() {
        String clientId = SecurityUtils.getClientId();
        Client client = clientMapper.getById(clientId);

        // 生成验证码
        String code = aliyunSmsConf.getSendCode();
        boolean result = true;
        if (aliyunSmsConf.getSend()) {
            code = SmsSendUtil.getVerifyNumber();
            result = SmsSendUtil.send(client.getMobile(), code, aliyunSmsConf);
        }
        if (result) {
            redisService.setCacheObject(MessageFormat.format(RedisKey.PAY_PASSWORD_CODE, client.getMobile()), code, 5L, TimeUnit.MINUTES);
        } else {
            throw new ServiceException("验证码发送失败，请稍后再试");
        }
    }

    @Override
    public Boolean resetPayPasswordVerifyCode(String code) {
        String clientId = SecurityUtils.getClientId();
        Client client = clientMapper.getById(clientId);
        String redisCode = redisService.getCacheObject(MessageFormat.format(RedisKey.PAY_PASSWORD_CODE, client.getMobile()));

        if (StringUtils.isEmpty(redisCode)) {
            throw new ServiceException(ResponseEnum.ANEW_CODE.getMsg());
        }

        if (redisCode.equals(code)) {
            redisService.deleteObject(MessageFormat.format(RedisKey.PAY_PASSWORD_CODE, client.getMobile()));
            return Boolean.TRUE;
        } else {
            throw new ServiceException(ResponseEnum.CODE_ERROR.getMsg());
        }
    }

    @Override
    public Client balanceRefundOrder(OrderCombo orderCombo) {
        String clientId = SecurityUtils.getClientId();
        Client client = clientMapper.getById(clientId);

        BigDecimal balance = NumberUtil.sub(NumberUtil.toBigDecimal(orderCombo.getPrice()), orderCombo.getGiftGold());

        client.setBalance(NumberUtil.add(client.getBalance(), balance));
        client.setGiftGold(NumberUtil.add(client.getGiftGold(), orderCombo.getGiftGold()));

        clientMapper.edit(client);
        return client;
    }

    @Override
    public Client balanceRefundOrder(OrderCombo orderCombo, OrderList orderList) {
        Client client = this.getById(orderCombo.getUserId());

        BigDecimal bigDecimal = NumberUtil.add(NumberUtil.toBigDecimal(orderList.getAmt()), orderList.getGiftGold());

        client.setBalance(NumberUtil.add(client.getBalance(), bigDecimal));
        client.setGiftGold(NumberUtil.add(client.getGiftGold(), orderList.getGiftGold()));

        clientMapper.edit(client);
        return client;
    }

    @Override
    public Client balanceRefundOrder(String userId, List<OrderGiftGold> orderGiftGolds) {
        Client client = this.getById(userId);

        BigDecimal money = orderGiftGolds.stream().map(OrderGiftGold::getMoney).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal giftGold = orderGiftGolds.stream().map(OrderGiftGold::getGiftGold).reduce(BigDecimal.ZERO, BigDecimal::add);

        client.setBalance(NumberUtil.add(client.getBalance(), money));
        client.setGiftGold(NumberUtil.add(client.getGiftGold(), giftGold));

        clientMapper.edit(client);

        return client;
    }

    /**
     * 清空储值余额
     *
     * @param clientId
     */
    @Override
    public void emptyBalance(String clientId) {
        clientMapper.emptyBalance(clientId);
    }

    /**
     * 储值退款回调
     *
     * @param wxPayPartnerRefundNotifyV3Result
     */
    @Override
    @Transactional
    public void storedRefundNotify(WxPayPartnerRefundNotifyV3Result wxPayPartnerRefundNotifyV3Result) {

        log.info("回调处理客户信息:{}", wxPayPartnerRefundNotifyV3Result);

        String orderId = wxPayPartnerRefundNotifyV3Result.getResult().getOutTradeNo();
        // 生成锁
        RLock lock = redissonClient.getLock(CacheConstants.CLIENT_STORED_REFUND_NOTIFY_LOCK_KEY + orderId);
        // 加锁操作 true成功  false失败
        boolean b = lock.tryLock();
        Assert.isTrue(b, "稍后重试");
        try {
            ClientBill cb = clientBillService.getByOrderSn(orderId);

            // 如果该储值记录余额为0，则表示已经退过款了
            if (cb.getCurrentBillMoney().compareTo(BigDecimal.valueOf(0)) == 0) {
                return;
            }

            // 清掉这条记录的金额
            clientBillService.emptyCurrentBillMoney(cb.getId());

            Client client = clientMapper.getById(cb.getClientId());

            // 扣除余额和赠送金
            client.setBalance(client.getBalance().subtract(cb.getCurrentBillMoney()));
            client.setGiftGold(client.getGiftGold().subtract(cb.getCurrentBillGiftGold()));
            clientMapper.deductMoneyAndGiftGold(client);

            Long cbId = cb.getId();
            // 插入退款记录
            clientBillService.createRefund(cb);

            clientBillService.updateStatus(cbId, 2);

            // 储值优惠券失效
            remoteOperationService.storedCouponExpire(cbId);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    @Override
    public Client balanceSub(BigDecimal money, BigDecimal giftMoney) {
        String clientId = SecurityUtils.getClientId();
        Client client = clientMapper.getById(clientId);
        client.setBalance(NumberUtil.sub(client.getBalance(), money));
        client.setGiftGold(NumberUtil.sub(client.getGiftGold(), giftMoney));

        clientMapper.edit(client);
        return client;
    }

    /**
     * 用户打点位标签
     *
     * @param clientId
     * @param lastPositionId
     */
    @Override
    public void updatePositionId(String clientId, String lastPositionId) {
        clientMapper.updatePositionId(clientId, lastPositionId);
    }

    @Override
    public void decreaseIntegralQuantity(String clientId, Long integral, String orderSn) {

        if (!NumberUtil.isGreater(NumberUtil.toBigDecimal(integral), BigDecimal.ZERO)) {
            return;
        }

        RLock lock = redissonClient.getLock(CacheConstants.CLIENT_INTEGRAL_LOCK_KEY + clientId);
        lock.lock();
        try {
            Client client = clientMapper.getById(clientId);
            if (!NumberUtil.isGreaterOrEqual(NumberUtil.toBigDecimal(client.getIntegral()), NumberUtil.toBigDecimal(integral))) {
                Assert.isTrue(false, "当前积分不足");
            }

            // 扣减积分
            BigDecimal sub = NumberUtil.sub(NumberUtil.toBigDecimal(client.getIntegral()), NumberUtil.toBigDecimal(integral));
            client.setIntegral(sub.intValue());
            clientMapper.edit(client);

            // 增加积分扣减记录
            ClientIntegral clientIntegral = new ClientIntegral();
            clientIntegral.setClientId(clientId);
            clientIntegral.setIntegral(integral.intValue());
            clientIntegral.setSource(2);
            clientIntegral.setOrderId(orderSn);
            clientIntegral.setStatus(ClientIntegralStatusEnum.NORMAL.getStatus());
            clientIntegral.setType(2);
            clientIntegral.setSurplusIntegral(client.getIntegral());
            clientIntegralService.insert(clientIntegral);

        } finally {
            lock.unlock();
        }
    }

    @Override
    public void recoverLockIntegralQuantity(String clientId, Long integral, String orderSn) {

        if (!NumberUtil.isGreater(NumberUtil.toBigDecimal(integral), BigDecimal.ZERO)) {
            return;
        }

        RLock lock = redissonClient.getLock(CacheConstants.CLIENT_INTEGRAL_LOCK_KEY + clientId);
        lock.lock();
        try {
            Client client = clientMapper.getById(clientId);
            ClientIntegral clientIntegral = clientIntegralService.findByOrderId(orderSn);

            // 恢复积分
            BigDecimal sub = NumberUtil.add(NumberUtil.toBigDecimal(client.getIntegral()), NumberUtil.toBigDecimal(clientIntegral.getIntegral()));
            client.setIntegral(sub.intValue());
            clientMapper.edit(client);

            // 增加积分恢复记录
            clientIntegral.setStatus(ClientIntegralStatusEnum.REFUND.getStatus());
            clientIntegral.setType(1);
            clientIntegral.setSurplusIntegral(client.getIntegral());
            clientIntegralService.insert(clientIntegral);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 退回积分
     *
     * @param clientId
     * @param integral
     */
    @Override
    public void returnIntegral(String clientId, Long integral) {
        clientMapper.returnIntegral(clientId, integral);
    }
}
